/*
 * DOS Solitaire - Klondike Solitaire in 80x25 text!
 *
 * Copyright 2000-2003 Dave Dunfield.
 * All rights reserved.
 *
 * Permission granted for personal (non-commercial) use only.
 *
 * Compile command: cc solitair -fop
 */
#include <stdio.h>
#include <window.h>

/* Various fixed parameters */
#define	CARDS	13			/* Number of cards in suite */
#define	SUITES	4			/* Number of suites in deck */
#define	DECK	52			/* Number of cards in deck */
#define	STACKS	7			/* Number of working stacks */
#define	CARDW	7			/* Width of card */
#define	CARDH	7			/* Height of card */
#define	XSPACE	8			/* X spacing of cards */
#define	YSPACE	8			/* Y spacing of cards */

/*
 * Global variables
 */
unsigned char
	card,					/* Working card */
	work[CARDS],			/* Stack of multiple working cards */
	deck[DECK],				/* Deck of available cards (face down) */
	draw[DECK],				/* Deck of drawn cards (face up) */
	stack[STACKS][CARDS+6],	/* Stacks of working cards */
	outcard[SUITES][CARDS];	/* Stacks of outgoing cards */

int
	work_top,				/* Count of extra cards drawn */
	deck_top = DECK,		/* Current position in deck */
	draw_top,				/* Current position in draw deck */
	stack_top[STACKS],		/* Current position in working stacks */
	out_top[SUITES];		/* Current position in outgoing stacks */

unsigned
	from = -1;				/* Last card came from here */

extern unsigned
	RAND_SEED;				/* Random number generator seed */

char help[] = { "\
DOS SOLITAIRE: Copyright 2000-2003 Dave Dunfield - All rights reserved.\n\n\
This is standard 'Klondike' solitaire, the idea is to move all cards from the\n\
inital face-down configuration to the sorted output sets. Cards can only be\n\
moved to output sets in order (Ace->King). Cards can be placed and retrieved\n\
in seven 'working' piles. Working piles must be placed in reverse order, and\n\
allow only cards of opposite color (red/black) to be played on top of one\n\
another. Only a King may be placed on an empty pile.\n\n\
Use the following command keys to pickup and place cards:\n\n\
T   = Turn over three cards from the face-down deck (reset if empty).\n\
C   = Cheat! Turn over only one card from face-down deck.\n\
D   = Pick up top card from the face-up deck.\n\
1-7 = Press while holding a card to place a card on the numbered pile.\n\
      If not holding a card, pickup a card from the numbered pile.\n\
      If just picked up, press again to pickup multiple cards.\n\
      Also used to turn over hidden cards in numbered piles.\n\
R   = Return a card to deck/pile from which it was taken.\n\
O   = Send a card out to the final sets (Must be holding exactly 1 card).\n\
      If not holding card, retreive card from output - press again to cycle.\n\
A   = Auto-out all possible cards from working piles to final sets.\n\
ESC = Give up and quit.\n\n\
Press a key to continue..." };

/*
 * Display a card on the screen
 */
void show_card(int x, int y, int card)
{
	int i, j, n;
	char s, attr;

	static char *X[] = { "A", "2", "3", "4", "5", "6", "7", "8", "9",
		"10", "J", "Q", "K" };
	static unsigned cpos[] = {
		0x010,		/* Ace */
		0x044,		/* Two */
		0x111,		/* Three */
		0x088,		/* Four */
		0x212,		/* Five */
		0x222,		/* Six */
		0x262,		/* Seven */
		0x28A,		/* Eight */
		0x29A,		/* Nine */
		0x2AA,		/* Ten */
		0 };
	static char *face[] = { "Jack.", "Queen", "King." };

	attr = *W_OPEN;
	n = card % CARDS;
	s = (card / CARDS) + '\x03';
	*W_OPEN = REVERSE + ((s > 4) ? 0 : F_RED);

	wgotoxy(x, y); wprintf("%-2s    %c", X[n], s);
	if(n < 10) {
		j = cpos[n];
		for(i=0; i < (CARDH-2); ++i) {
			wgotoxy(x, ++y); wprintf(" ");
			switch(j & 0x03) {
				case 0x00 : wprintf("     ");			break;
				case 0x01 : wprintf("  %c  ", s);		break;
				case 0x02 : wprintf("%c   %c", s, s);	break;
				case 0x03 :	wprintf("%c %c %c", s, s, s); }
			wprintf(" ");
			j >>= 2; } }
	else {
		for(i=0; i < (CARDH-2); ++i) {
			wgotoxy(x, ++y);
			switch(i) {
				case 0 :
				case 4 :
					wprintf("  %c%c%c  \n", s, s, s);
					break;
				case 2 :
					wprintf(" %-5s \n", face[n-10]);
					break;
				default:
					wprintf(" ----- "); } } }

	wgotoxy(x, ++y);
	wprintf("%c    %2s", s, X[n]);

	*W_OPEN = attr;
}

/*
 * Display a blank card on the screen
 */
void blank_card(int x, int y, char fill)
{
	unsigned i, j;
	for(i=0; i < CARDH; ++i) {
		wgotoxy(x, y+i);
		for(j=0; j < CARDW; ++j)
			wputc(fill); }
}

/*
 * Draw the working screen
 */
void show_screen()
{
	unsigned i, x, y, d;
	int k;
	char c;
	static unsigned char depth[] = {
		0xB0, 0xB0, 0xB0, 0xB1, 0xB1, 0xB2, 0xB2 };

	wclwin();

	/* Show the face-down deck if it has cards */
	if(deck_top)
		blank_card(0, 1, 0xB2);

	/* Show the stacked cards */
	for(i=0; i < STACKS; ++i) {
		y = 1;
		if(stack_top[i]) {
			x = (i*XSPACE)+XSPACE;
			for(k=d=0; k < stack_top[i]; ++k) {
				c = stack[i][k];
				if(c & 0x80) {
					++d;
					continue; }
				if(d) {
					blank_card(x, y++, depth[d]);
					d = 0; }
				show_card(x, y++, c); }
			if(d)
				blank_card(x, y, depth[d]); } }

	/* Show the output cards */
	for(i=0; i < SUITES; ++i) {
		x = ((i & 1) * XSPACE) + (XSPACE*8);
		y = ((i < 2) * YSPACE) + 10;
		if(k = out_top[i])
			show_card(x, y, outcard[i][k-1]);
		else
			blank_card(x, y, 0xB0); }

	/* Show the working deck */
	k = draw_top - 3;
	if(k < 0)
		k = 0;
	y = YSPACE;
	while(k < draw_top)
		show_card(0, ++y, draw[k++]);

	/* Show the active card(s) */
	y = 0;
	for(k=0; k < work_top; ++k)
		show_card(67, y++, work[k]);
	if(card != -1)
		show_card(67, y, card);

	/* Draw the prompt characters */
	wgotoxy(3, 0);
	wputc('T');
	wgotoxy(3, 8);
	wputc('D');
	wgotoxy(71, 17);
	wputc('O');
	for(i=1; i < 8; ++i) {
		wgotoxy(i*XSPACE+3, 0);
		wputc(i+'0'); }
	wgotoxy(0, 24);
	wputs("Press 'H' for help");
}

/*
 * Display a message with bouncing cards
 */
void bounce_cards(char *message)
{
	unsigned i, b, l;
	int x, y, c;

	l = 40 - (strlen(message)/2);
	for(i=0; i < 52; ++i) {
		wclwin();
		y = 0;
		b = random(3)+1;
		for(x=0; x < 73; ++x) {
			if(c = wtstc())
				return c;
			show_card(x, y, i);
			wgotoxy(l, 12);
			wputs(message);
			delay(50);
			y += b;
			if((y < 0) || (y > 18)) {
				y -= (b+b);
				b = -b; } } }
}

/*
 * Generate an error beep
 */
void Beep()
{
	beep(1000, 150);
}

/*
 * Main program
 */
main()
{
	int i, j, s;
	unsigned char c, f;

	RAND_SEED = peek(0x40, 0x6C);

#ifdef DEBUG
	debug_init();
#endif

	wopen(0, 0, 80, 25, WSAVE|WCOPEN|NORMAL);
	wcursor_off();

	bounce_cards(" SOLITAIRE: Press a key to begin! ");

	/* Deal the deck of cards */
	for(i=0; i < DECK; ++i)		/* First init deck to all cards */
		deck[i] = i;
	for(i=0; i < DECK; ++i) {	/* Then shuffle */
		j = random(DECK);
		s = deck[i];
		deck[i] = deck[j];
		deck[j] = s; }

	/* Deal out the stacks, and turn over top cards */
	for(i=0; i < STACKS; ++i) {
		for(j=i+1; j; --j)
			stack[i][stack_top[i]++] = deck[--deck_top] | 0x80;
		stack[i][stack_top[i]-1] &= 0x7F; }

	card = -1;

	for(;;) {
		show_screen();
		/* Test for winner (All output sets full) */
		for(i=0; i < SUITES; ++i)
			if(out_top[i] != CARDS)
				break;
		if(i >= SUITES) {
			bounce_cards(" A WINNER! - Press a key to exit! ");
			wclose();
			return; }
#ifdef DEBUG
		switch(i = debug_move()) {
			case 'Q' :						/* Query (debug) */
				debug(-1);
				break;
#else
		switch(i = toupper(wgetc())) {
#endif
			case 0x1B :						/* Terminate */
				wclose();
				return;
			case 'H' :						/* Help request */
			case '?' :
				wclwin();
				wputs(help);
				wgetc();
				break;
			case '1':						/* Select working stack */
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				i -= '1';
				j = stack_top[i];
				if(card == -1) {			/* Picking up a new card */
					if(j) {
						if(stack[i][j-1] & 0x80) {
							stack[i][j-1] &= 0x7F;
							break; }
						card = stack[from = i][--stack_top[i]]; }
					else
						Beep();
					break; }
				if(i == from) {				/* Picking up another card */
					if(!j) {					/* Stack empty */
						Beep();
						break; }
					if(stack[i][j-1] & 0x80) {	/* Stack covered */
						Beep();
						break; }
					work[work_top++] = card;
					card = stack[i][--stack_top[i]];
					break; }
				/* Putting down a card */
				if(!j) {					/* Empty stack, only King */
					if((card % CARDS) == 12)
						goto putcard;
					Beep();
					break; }
				c = stack[i][j-1];
				if( ((card/CARDS)<2) == ((c/CARDS)<2) ) {	/* Colors same */
					Beep();
					break; }
				if( ((card%CARDS)+1) != (c%CARDS) ) {		/* Out of order */
					Beep();
					break; }
			putcard:
				from = -1;
				stack[i][stack_top[i]++] = card;
				card = (work_top) ? work[--work_top] : -1;
				break;
			case 'R' :		/* Replace card to stack or working deck */
				if(card != -1) {
					if(from < STACKS) {		/* Replace to stack */
						stack[from][stack_top[from]++] = card;
						card = (work_top) ? work[--work_top] : -1;
						break; }
					if(from == STACKS) {	/* Replace to working deck */
						draw[draw_top++] = card;
						card = -1;
						break; }
					if(from > STACKS) {		/* Replace to output deck */
						s = from - (STACKS+1);
						outcard[s][out_top[s]++] = card;
						card = -1;
						break; } }
			default:		/* Invalid entry */
				Beep();
				break;
			case 'D' :	/* Pickup card from deck */
				if((card == -1) && draw_top) {
					card = draw[--draw_top];
					from = STACKS;
					break; }
				Beep();
				break;
			case 'C' :			/* Cheat draw - only one card */
				j = 1;
				goto draw_card;
			case 'T' :			/* Draw three cards */
				j = 3;
			draw_card:
				if(card != -1) {
					Beep();
					break; }
				if(!deck_top) {	/* Draw deck is empty */
					while(draw_top)
						deck[deck_top++] = draw[--draw_top];
					break; }
				for(i=0; i < j; ++i) {
					if(!deck_top)
						break;
					draw[draw_top++] = deck[--deck_top]; }
				break;
			case 'A' :			/* Auto-out */
				if(card != -1) {
					Beep();
					break; }
				do {
					for(s=f=0; s < SUITES; ++s) {
						if(j = out_top[s])
							c = outcard[s][j-1]+1;
						else
							c = s * CARDS;
						for(i=0; i < STACKS; ++i) {
							if(!(j = stack_top[i]))
								continue;
							card = stack[i][j-1];
							if(card == c) {
								--stack_top[i];
								show_screen();
								delay(100);
								outcard[s][out_top[s]++] = card;
								f = -1; } } } }
				while(f);
				card = -1;
				break;
			case 'O' :			/* Send a card out */
			case '0' :
				if(work_top) {				// More than one card held
					Beep();
					break; }
				if(card == -1) {			// No card held - pickup
					j = i = 4;
					goto get_out_card; }
				else if(from > STACKS) {	// Rotate to next
					i = 3;
					j = (s = from - (STACKS+1)) + 1;
					outcard[s][out_top[s]++] = card;
					card = -1;
				get_out_card:
					do {
						if(out_top[j &= 3]) {
							card = outcard[j][--out_top[j]];
							from = j + (STACKS+1);
							break; }
						++j; }
					while(--i);
					break; }
				s = card/CARDS;
				if(!(j = out_top[s])) {
					if(!(card % CARDS)) {
						outcard[s][out_top[s]++] = card;
						card = -1;
						break; }
					Beep();
					break; }
				c = outcard[s][j-1] % CARDS;
				if((c+1) != (card % CARDS)) {
					Beep();
					break; }
				outcard[s][out_top[s]++] = card;
				card = -1; } }
}

#ifdef DEBUG
/*
 * Debugging code:
 *
 * When the 'DEBUG' flag is set, the following special features are added
 * to the SOLITAIRE game:
 * - New Q)uery command, displays card/deck/stack position/status,
 *   and checks for lost or duplicated cards.
 * - Allows W)rite of current game state to file: SOL.DAT
 * - card/deck/stack status checked for error before every move,
 *   if a problem is found, debug mode is automatically entered.
 * - If SOL.DAT exists when game started, game will be restored with
 *   the same random-seed, and re-played to the point where the file
 *   was saved.
 */

/*
 * Global variables used by debug functions.
 */
static unsigned
	starting_seed,			/* Records initial random seed */
	track_deck[DECK],		/* Track card occurances */
	move_count = 0;			/* Count of recorded movements */
static char
	track_move[2000];		/* Move recording table */

/*
 * Initialize the debug session
 *
 * - Save current initial random_number key
 * - if SOL.DAT exists, load it into memory, set seed and move table.
 */
debug_init()
{
	int c;
	char buffer[80];
	FILE  *fp;

	if(fp = fopen("SOL.DAT", "r")) {
		fgets(buffer, sizeof(buffer)-1, fp);
		RAND_SEED = atoi(buffer);
		while((c = getc(fp)) != -1) {
			if(c != '\n')
				track_move[move_count++] = c; }
		fclose(fp);
		move_count = 0; }
		
	starting_seed = RAND_SEED;
}

/*
 * Generate or record a debugging move
 *
 * - If entry already exists in move table, return it (auto-play)
 * - If at end of recorded move table, get entry and add to table.
 */
int debug_move()
{
	int c;

	if(track_move[move_count])
		return track_move[move_count++];

	c = toupper(wgetc());
	debug(0);
	track_move[move_count++] = c;
	return c;
}

/*
 * Display a card, or hex value if invalid.
 * - update tracking deck to indicate card has been processed.
 */
void debug_card(unsigned c)
{
	static char *X[] = { "A", "2", "3", "4", "5", "6", "7", "8", "9",
		"10", "J", "Q", "K" };
	wputc(' ');
	if((c & 0x7F) >= DECK) {
		wprintf("[%02x]", c);
		return; }
	if(c & 0x80) {
		wputc('~');
		c &= 0x7F; }
	++track_deck[c];
	wputs(X[c % CARDS]);
	wputc(c / CARDS + 0x103);
}

/*
 * Main debug function:
 * - Display location of all cards / decks / stacks
 * - Check for lost or duplicate cards
 *
 * If called with wait_for_command==0, the function does not wait
 * for a command unless an error in the card locations has been
 * detected. This is used by debug_move() to test the state of the
 * decks/stacks after every movement.
 */
debug(char wait_for_command)
{
	unsigned i, j;
	char *mptr;
	FILE *fp;

	mptr = 0;
redraw:
	wclwin();
	memset(track_deck, 0, sizeof(track_deck));

	/* Display face down deck */
	wprintf("Deck:");
	i = deck_top;
	while(i)
		debug_card(deck[--i]);

	/* Display face-up deck */
	wprintf("\nDraw:");
	i = draw_top;
	while(i)
		debug_card(draw[--i]);

	/* Display active cards */
	wprintf("\nCard:");
	debug_card(card);
	i = work_top;
	while(i)
		debug_card(work[--i]);
	/* Display card from location */
	wprintf("  From: %d\n", from);

	/* Display working stacks */
	for(i=0; i < STACKS; ++i) {
		wprintf("\nStack%u:", i+1);
		j = stack_top[i];
		while(j)
			debug_card(stack[i][--j]); }
	wputc('\n');

	/* Display output stacks */
	for(i=0; i < SUITES; ++i) {
		wprintf("\nOut");
		wputc(i+0x103);
		wputc(':');
		j = out_top[i];
		while(j)
			debug_card(outcard[i][--j]); }
	wputc('\n');

	/* Display lost or duplicate cards */
	for(i=j=0; i < DECK; ++i) {
		if(track_deck[i] != 1) {
			if(!j) {
				wprintf("\nError:");
				wait_for_command = j = -1; }
			debug_card(i);
			wprintf("(%u)", track_deck[i]-1); } }

	/* For non-error/manual, do not enter interactive prompt */
	if(!wait_for_command)
		return;

	/* Display movement table */
	wprintf("\nMove: ");
	for(i=j=0; i < move_count; ++i) {
		wputc(track_move[i]);
		if(++j > 70) {
			j = 0;
			wprintf("\n      "); } }

	/* Issue prompt and get command */
	wgotoxy(0, 24); wputs("W)rite Q)uit");
	if(mptr) {		/* Informational message to display */
		wgotoxy(50, 24);
		wputs(mptr);
		mptr = 0; }

	switch(toupper(wgetc())) {
	case 'W' :		/* Write game status to a file */
		fp = fopen("SOL.DAT", "wvq");
		fprintf(fp, "%u\n", starting_seed);
		j = 0;
		for(i=j=0; i < move_count; ++i) {
			putc(track_move[i], fp);
			if(++j > 70) {
				j = 0;
				putc('\n', fp); } }
		putc('\n', fp);
		fclose(fp);
		mptr = "SOL.DAT written";
		goto redraw;
	case 'Q' :		/* Quit debugger and return to game play */
	case 0x1B:
		return; }

	mptr = "Unrecognized command";
	Beep();
	goto redraw;
}
#endif
